/**
 * @file tpl_system_call.S
 *
 * @section descr File description
 *
 * System calls handling.
 *
 * @section copyright Copyright
 *
 * Trampoline RTOS
 *
 * Trampoline is copyright (c)
 * CNRS, University of Nantes, Ecole Centrale de Nantes
 * Trampoline is protected by the French intellectual property law.
 *
 * This software is distributed under the GNU Public Licence V2.
 * Check the LICENSE file in the root directory of Trampoline
 *
 * @section infos File informations
 *
 * $Date$
 * $Rev$
 * $Author$
 * $URL$
 */

.syntax unified
.thumb

#include "tpl_assembler.h"
#include "tpl_asm_definitions.h"
#include "tpl_os_kernel_stack.h"
#include "tpl_service_ids.h"

.equ  NO_NEED_SWITCH_NOR_SCHEDULE, 0
.equ  NO_NEED_SWITCH, 0
.equ  NEED_SWITCH, 1
.equ  NEED_SAVE, 2

/*-----------------------------------------------------------------------------
 * the tpl_sc_handler uses tpl_kern
 */

#define OS_START_SEC_VAR
#include "tpl_as_memmap.h"

.extern tpl_kern

#define OS_STOP_SEC_VAR
#include "tpl_as_memmap.h"

/*-----------------------------------------------------------------------------
 * the tpl_sc_handler uses the dispatch table
 */

#define OS_START_SEC_CONST
#include "tpl_as_memmap.h"

.extern tpl_dispatch_table

#define OS_STOP_SEC_CONST
#include "tpl_as_memmap.h"

/*-----------------------------------------------------------------------------
 * the code starts here
 */

#define OS_START_SEC_CODE
#include "tpl_as_memmap.h"

/*=============================================================================
 * The system call handler is executed when the application (Task or ISR2)
 * executes a SVC by calling a service. The microcontroler is configured
 * in non priviledged thread / priviledged handler with a thread (process)
 * stack (pointed by PSP) and a handler (kernel) stack (pointed by MSP).
 * Switching from process stack to kernel stack is done automatically when
 * a SVC is executed and switching from the kernel stack to the process stack
 * is done automatically when returning from the SVC handler (tpl_sc_handler).
 *
 * It assumes the following:
 *
 * 1 - The process stack is populated according to the Cortex M standard
 *
 * +------------------+
 * | R0               | <- PSP
 * +------------------+
 * | R1               | <- PSP+4
 * +------------------+
 * | R2               | <- PSP+8
 * +------------------+
 * | R3               | <- PSP+12
 * +------------------+
 * | R12              | <- PSP+16
 * +------------------+
 * | LR               | <- PSP+20
 * +------------------+
 * | Return Address   | <- PSP+24
 * +------------------+
 * | xPSR (bit 9 = 1) | <- PSP+28
 * +------------------+
 *
 * 2 - The system call wrapper uses r3 to pass the identifier of the service.
 *     This identifier is used as an index in a function pointer table, each of
 *     these function pointer being a service of Trampoline. Services callable
 *     by ISR1 are the 6 first (so correspond to identifiers 0-5)
 *
 * 3 - registers r0, r1 and r2 contains, according to the ABI, the arguments
 *     of the service (in Trampoline, a service is limited to 3 arguments).
 *     As a result registers r0, r1 and r2 should not be changed by the SVC
 *     handler but may be changed after the call of the service (step 8).
 */
.global knl_start_dispatch
.type knl_start_dispatch, %function
.global tpl_sc_handler
.type   tpl_sc_handler, %function

knl_start_dispatch:
tpl_sc_handler:

    /*-----------------------------------------------------------------------*
     * Once here the stack has been changed to the master stack.             *
     * So SP is MSP, not PSP                                                 *
     *-----------------------------------------------------------------------*/

    /*-------------------------------------------------------------------------
     * 1 - The service identifier is checked to prevent crashes if it has
     *     a wrong value
     */
    cmp   r3,#SYSCALL_COUNT
    bhs   tpl_sc_handler_invalid_service_id

    /*-------------------------------------------------------------------------
     * 1a - The service identifier is checked again for xxxInterrupt services
     *      that are callable by ISR1. If the service  is callable by ISR1,
     *      interrupts are disabled before calling the service so that it
     *      can't be interrupted by an ISR1.
     */
    cmp   r3,#SYSCALL_COUNT_ISR1
    bhs   not_callable_by_isr1
    cpsid i   /* set PRIMASK */

not_callable_by_isr1:

    /*-------------------------------------------------------------------------
     * 2 - Build the kernel stack frame, see file tpl_os_kernel_stack.h
     */
    sub   sp,sp,#KS_FOOTPRINT

    /*-------------------------------------------------------------------------
     * 3 - Save working registers in the master stack frame.
     *     We need a couple of low register to work and to be compatible
     *     with armv6m. We choose r4 and r5
     */
    str   r4,[sp,#KS_R4]
    str   r5,[sp,#KS_R5]

    /*-------------------------------------------------------------------------
     * 4 - Save the link register, in armv6m, the source register of a store
     *     must be r0-r7, so we use r4 (already saved) to copy lr
     */
    mov   r4,lr
    str   r4,[sp,#KS_LR]

    /*-------------------------------------------------------------------------
     * 6 - Reset the tpl_kern variables
     */
    ldr   r4,=tpl_kern
    movs  r5,#NO_NEED_SWITCH_NOR_SCHEDULE
    strb  r5,[r4,#TPL_KERN_OFFSET_NEED_SWITCH]
    strb  r5,[r4,#TPL_KERN_OFFSET_NEED_SCHEDULE]

    /*-------------------------------------------------------------------------
     * 7 - Get the function pointer to the service
     */
    ldr   r4,=tpl_dispatch_table
    lsls  r3,r3,#2
    ldr   r3,[r4,r3]

    /*-------------------------------------------------------------------------
     * 8 - Service call
     */
    blx   r3

    /*-----------------------------------------------------------------------*
     * From here, r1, r2 and r3 are available as working register            *
     * r0 hold the result of the service and should not be changed           *
     *-----------------------------------------------------------------------*/

    /*-------------------------------------------------------------------------
     * 9 - Copy back the result to the saved r0 on the process stack
     *     r0 is at the top of the process stack
     */
    mrs   r4,psp
    str   r0,[r4]

    /*-------------------------------------------------------------------------
     * 10 - Check the context switch condition in tpl_kern
     */
    ldr   r4,=tpl_kern
    ldrb  r5,[r4,#TPL_KERN_OFFSET_NEED_SWITCH]
    cmp   r5,#NO_NEED_SWITCH
    beq   tpl_sc_handler_no_context_switch

    /*-------------------------------------------------------------------------
     * 11 - Prepare the call to tpl_run_elected by setting r0 to 0, aka no save
     */
    movs  r0,#0

    /*-------------------------------------------------------------------------
     * 12 - Check the save condition
     */
    movs  r4,#NEED_SAVE
    tst   r5,r4
    beq   tpl_sc_handler_no_save_running_context /* bit of NEED_SAVE set to 0 */

    /*-------------------------------------------------------------------------
     * 13 - Save context
     *      Load in r0 the pointer to the static descriptor of the running task
     */
    ldr   r0,=tpl_kern
    ldr   r0,[r0,#TPL_KERN_OFFSET_S_RUNNING]
    bl    tpl_save_context

    /*-------------------------------------------------------------------------
     * 14 - Prepare the call to tpl_run_elected by setting r0 to 1
     */
    movs  r0,#1

tpl_sc_handler_no_save_running_context:

    /*-------------------------------------------------------------------------
     * 15 - Call tpl_run_elected.
     *      The argument is a boolean which is true if the task is
     *      preempted. see stages 11 and 14 above.
     */
    bl    tpl_run_elected

    /*-------------------------------------------------------------------------
     * 16 - Load context
     *      Load in r0 the pointer to the context of the running task.
     *      It has been changed by tpl_run_elected
     */
    ldr   r0,=tpl_kern
    ldr   r0,[r0,#TPL_KERN_OFFSET_S_RUNNING]
    bl    tpl_load_context

tpl_sc_handler_no_context_switch:

    /*-------------------------------------------------------------------------
     * 18 - Restore the link register from the kernel stack.
     *      Symetric of stages 4
     */
    ldr   r4,[sp,#KS_LR]
    mov   lr,r4

    /*-------------------------------------------------------------------------
     * 19 - Restore r4 and r5 from the kernel stack.
     *      Symetric of stage 3
     */
    ldr   r5,[sp,#KS_R5]
    ldr   r4,[sp,#KS_R4]

    /*-------------------------------------------------------------------------
     * 20 - Deallocate frame on the MSP
     */
    add   sp,sp,#KS_FOOTPRINT

    /*-------------------------------------------------------------------------
     * 20b - Enable interrupts. This can be done without checking it has been
     *       disabled in 1a because application level interrupt masking is
     *       done using the BASEPRI register.
     */
    cpsie i   /* clear PRIMASK */

tpl_sc_handler_invalid_service_id:

    /*-------------------------------------------------------------------------
     * 21 - Return from the SVC
     */
    bx    lr

#define OS_STOP_SEC_CODE
#include "tpl_as_memmap.h"

/* End of file tpl_sc_handler.S */
